<script setup>
import ChatInput from '@/views/chat/components/ChatInput.vue'
import ChatMessage from '@/views/chat/components/ChatMessage.vue'
import {
  defaultChat,
  knowledgeChat,
  fileChat,
  httpQuestions,
  httpGetHistories,
  httpGetSession,
  httpDeleteSession,
  httpGetLastId,
  httpModels,
} from '@/api/chat.js'
import { getKnowledgeInfo } from '@/api/knowledge.js'
import { httpSelectApp } from '@/api/application.js'
import moment from 'moment'
import { ref, onMounted, reactive, computed, watch, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import useKnowledgeStore from '@/stores/knowledge.js'
import { useRoute } from 'vue-router'
import KnowledgeSelect from '@/views/chat/components/KnowledgeSelect.vue'
import useAppStore from '@/stores/application.js'
import { isObjectJson } from '@/utils/index.js'
import useChat from '@/stores/chat.js'
import { storeToRefs } from 'pinia'
import { CHAT_MODEL } from '@/enum/study'

const { question } = storeToRefs(useChat())
const knowledgeStore = useKnowledgeStore()
const appStore = useAppStore()
const route = useRoute()
let addStart = false

const disable = ref(false)
const file_id = ref('')
const chat_files = ref([])
const histories = ref([])
const chat_histories = ref([])
const selected = ref(0)
const ai_output = ref('')
const list_loading = ref(false)
const chat_load = ref(false)
const is_out_act = ref(false)
const query = reactive({
  keyword: '',
})
const filter_word = ref('')
const knowledge_id = ref(0)
const knowledge_name = ref('')
const questions = ref([])
const question_loading = ref(false)
const files = ref([])
const showAppInput = ref(false)
/**
 * @type {Ref<UnwrapRef<FormInstance>>}
 */
const appInputForm = ref(null)
const appInputData = reactive({})
const appInputRules = {}
const appInfo = reactive({})

const initAppInputForm = () => {
  Object.keys(appInputData).forEach((key) => {
    delete appInputData[key]
  })
  Object.keys(appInputRules).forEach((key) => {
    delete appInputData[key]
  })
  if (!appInfo.option_message) return
  appInfo.option_message.forEach((item, index) => {
    const field = `field_${index}`
    appInputData[field] = item.input_type === 'checkbox' ? [] : ''
    appInputRules[field] = [
      {
        required: item.is_must,
        message: '此项为必填项',
      },
    ]
  })
  nextTick(() => {
    setTimeout(() => {
      if (appInputForm.value && appInputForm.value.clearValidate) {
        appInputForm.value.clearValidate()
      }
    }, 300)
  })
}

const getQuestions = async () => {
  if (!histories.value.length) return
  if (import.meta.env.VITE_ENV === 'development') {
    questions.value = ['你是谁？', '你叫什么名字？', '你和ChatGPT有什么关系？']
    disable.value = false
    return
  }
  const old_questions = JSON.stringify(questions.value)
  questions.value = []
  question_loading.value = true
  const response = await httpQuestions({
    histories: httpHistories(),
    knowledge_id: file_id.value
      ? file_id.value
      : knowledge_id.value
      ? knowledge_id.value
      : 0,
    file_name: file_id.value ? chat_files.value[0].name : '',
  }).catch(() => null)
  question_loading.value = false
  disable.value = false
  if (!response) {
    questions.value = JSON.parse(old_questions)
  } else {
    const { list } = response
    if (list && list.length) {
      questions.value = list
    } else {
      questions.value = JSON.parse(old_questions)
    }
  }
}

const getChatHistories = async () => {
  list_loading.value = true
  const response = await httpGetHistories().catch(() => null)
  list_loading.value = false
  if (!response) return
  const { list } = response
  if (list.length) {
    chat_histories.value = list.map((item) => {
      return {
        id: item.id,
        title: item.comment,
        application_id: item.application_id,
        application_msg: item.application_msg
          ? JSON.parse(item.application_msg)
          : {},
      }
    })
    if (addStart) {
      await addSession()
    } else {
      selectChatItem(chat_histories.value[0])
    }
  } else {
    await addSession()
  }
}

const getHistories = async () => {
  chat_load.value = true
  const response = await httpGetSession(selected.value).catch(() => null)
  chat_load.value = false
  if (!response) {
    return
  }
  const { list } = response
  histories.value = list
  const kn = list[list.length - 1]
  if (kn && kn.knowledge_id) {
    selectKnowledge({ id: kn.knowledge_id, name: kn.knowledge_name })
  }
  await getQuestions()
}

const selectChatItem = (item) => {
  if (disable.value) return
  showAppInput.value = false
  Object.keys(appInfo).forEach((key) => {
    delete appInfo[key]
  })
  if (item.id) {
    selectKnowledge(knowledgeStore.getKnowledge())
    if (item.id === selected.value) return
    selected.value = item.id
    if (item.temp) {
      file_id.value = item.temp.file_id
      chat_files.value = item.temp.files
    } else {
      file_id.value = ''
      chat_files.value = []
    }
    if (item.application_id) {
      Object.keys(item.application_msg).forEach((key) => {
        appInfo[key] = item.application_msg[key]
      })
    }
    getHistories()
  } else {
    if (item.temp) {
      file_id.value = item.temp.file_id
      chat_files.value = item.temp.files
    }
    addSession(false)
  }
}
const handleFilter = () => {
  filter_word.value = query.keyword
}

watch(
  () => query.keyword,
  (newValue) => {
    if (newValue === '') {
      handleFilter()
    }
  },
)

const handleDelete = async (id, index) => {
  list_loading.value = true
  const res = await httpDeleteSession(id)
    .then(() => true)
    .catch(() => false)
  list_loading.value = false
  if (res) {
    chat_histories.value.splice(index, 1)
    await addSession()
  }
}

const addSession = async (file_empty = true, addApp = false) => {
  if (disable.value) return
  if (!addApp) {
    showAppInput.value = false
    Object.keys(appInfo).forEach((key) => {
      delete appInfo[key]
    })
  }
  selectKnowledge(knowledgeStore.getKnowledge())
  if (!chat_histories.value.length || chat_histories.value[0].id !== 0) {
    chat_histories.value = [
      {
        id: 0,
        title: '新会话',
      },
    ].concat(chat_histories.value)
  }
  if (file_empty) {
    file_id.value = ''
    chat_files.value = []
  }
  selected.value = 0
  histories.value = []
  if (!showAppInput.value || !appInfo.id) {
    await getQuestions()
  } else {
    questions.value = []
  }
}

const clearKnowledge = () => {
  selectKnowledge({ id: 0, name: '' })
  knowledgeStore.setKnowledge(0, '')
}

const getChatFunction = computed(() => {
  if (file_id.value) {
    return fileChat
  } else if (knowledge_id.value) {
    return knowledgeChat
  } else {
    return defaultChat
  }
})

const httpHistories = () => {
  const list = histories.value.slice(-8)
  return list.map((item) => {
    const { role, content, docs } = item
    return {
      role,
      content: content.replaceAll('{', '{{').replaceAll('}', '}}'),
      docs: '', // !docs ? '' : docs.replaceAll("{", "{{").replaceAll("}", "}}")
    }
  })
}

let controller = null

const stopRequest = () => {
  if (controller !== null) controller.abort()
  histories.value.push({
    content: ai_output.value,
    role: 'assistant',
    docs: '',
    knowledge_id: knowledge_id.value,
    file_names: files.value,
    time: moment().format('YYYY-MM-DD HH:mm:ss'),
  })
  ai_output.value = ''
  is_out_act.value = false
  disable.value = false
}

const applicationMsg = computed(() => {
  if (!appInfo.id) {
    return {}
  }
  return {
    id: appInfo.id,
    avatar: appInfo.avatar,
    character: appInfo.character,
    introduction: appInfo.introduction,
    knowledge_id: appInfo.knowledge_id,
    title: appInfo.title,
    target: appInfo.target,
    option_message: appInfo.option_message,
  }
})

/**
 * @param {String} content
 */
const requestChat = (content) => {
  disable.value = true
  getChatFunction
    .value({
      knowledge_id: file_id.value
        ? file_id.value
        : knowledge_id.value
        ? knowledge_id.value
        : null,
      message: content,
      conversation_id: selected.value ? selected.value : undefined,
      history: httpHistories(),
      score_threshold: 0.7,
      application_id: appInfo.id ? appInfo.id : 0,
      application_msg: applicationMsg.value,
      model_name: localStorage.getItem(CHAT_MODEL.GENERAL) || 'openchat',
    })
    .then(({ reader, header, control }) => {
      controller = control
      if (header.get('X-Custom-Header')) {
        let docs = ''
        if (header.get('Content-Documents')) {
          docs = decodeURIComponent(header.get('Content-Documents'))
          if (!docs) {
            docs = ''
          }
        }
        files.value = []
        if (header.get('Content-Files')) {
          files.value = JSON.parse(
            decodeURIComponent(header.get('Content-Files')),
          )
        }
        is_out_act.value = true
        selected.value = parseInt(header.get('X-Custom-Header'))
        if (histories.value.length <= 1) {
          chat_histories.value[0].id = selected.value
          chat_histories.value[0].title = content.substring(0, 16)
        }
        histories.value.push({
          role: 'user',
          docs: docs,
          file_names: [],
          knowledge_id: knowledge_id.value,
          content: content,
          time: moment().format('YYYY-MM-DD HH:mm:ss'),
        })
      }
      handleReader(reader)
    })
    .catch(() => {
      is_out_act.value = false
      disable.value = false
    })
}

const handleReader = async (reader) => {
  let d, v
  try {
    const { value, done } = await reader.read()
    d = done
    v = value
  } catch (e) {
    d = true
  }
  const decoder = new TextDecoder()
  if (!d) {
    let data = decoder.decode(v.buffer)
    ai_output.value += data
    console.log(data)
    await handleReader(reader)
  } else {
    if (v) {
      let data = decoder.decode(v.buffer)
      console.log(data)
      ai_output.value += data
    }
    const json = isObjectJson(ai_output.value)
    if (json !== false && json.code !== 200) {
      ai_output.value = ''
      disable.value = false
      is_out_act.value = false
      ElMessage.error(json.message)
    } else {
      if (ai_output.value) {
        const { hid } = await httpGetLastId(selected.value).catch(() => {
          return { hid: 0 }
        })
        histories.value.push({
          id: hid,
          content: ai_output.value,
          knowledge_id: knowledge_id.value,
          role: 'assistant',
          file_names: files.value,
          time: moment().format('YYYY-MM-DD HH:mm:ss'),
        })
      }
      files.value = []
      ai_output.value = ''
      is_out_act.value = false
      disable.value = false
      await getQuestions()
    }
  }
}

const updateFile = (files) => {
  chat_histories.value.forEach((item, index) => {
    if (item.id === selected.value) {
      chat_histories.value[index].temp = {
        file_id: file_id.value,
        files: files,
      }
    }
  })
}
const handleShowAppInput = () => {
  showAppInput.value = true
  initAppInputForm()
}
const handleSelectApp = async (item) => {
  Object.keys(item).forEach((key) => {
    appInfo[key] = item[key]
  })
  if (appInfo.option_message && appInfo.option_message.length) {
    showAppInput.value = true
  }
  await addSession(true, true)
  initAppInputForm()
  if (!item.knowledge_id) {
    selectKnowledge({ id: 0, name: '' })
    return
  }
  const idList = JSON.parse(item.knowledge_id)
  if (!idList[0]) {
    selectKnowledge({ id: 0, name: '' })
    return
  }
  const kid = parseInt(idList[0])
  if (kid && !isNaN(kid) && knowledge_id.value !== kid) {
    const { name } = await getKnowledgeInfo(kid).catch(() => {
      return { name: '' }
    })
    selectKnowledge({ id: kid, name: name })
  } else {
    selectKnowledge({ id: 0, name: '' })
  }
}

const handleClearApp = () => {
  showAppInput.value = false
  if (!histories.value.length) {
    Object.keys(appInfo).forEach((key) => {
      delete appInfo[key]
    })
    selectKnowledge(knowledgeStore.getKnowledge())
  }
}

const handleSubmitApp = async () => {
  const valid = await appInputForm.value.validate((valid) => valid)
  if (!valid) return
  // 拼接app输入内容
  let content = ''
  appInfo.option_message.forEach((item, index) => {
    if (content !== '') {
      content += '\n'
    }
    if (item.take_title) {
      content += item.title + ':'
    }
    let input = appInputData[`field_${index}`]
    if (item.input_type === 'checkbox') {
      input = input.join(',')
    }
    content += input
  })
  if (!content) {
    return
  }
  await httpSelectApp(appInfo.id).catch(() => null)
  requestChat(content)
  showAppInput.value = false
}

const showKnowledge = ref(false)
const knowledgeIsPrivate = ref(-1)

const selectKnowledge = ({ id, name }) => {
  knowledge_id.value = id
  knowledge_name.value = name
  showKnowledge.value = false
}

const handShowKnowledge = (type) => {
  knowledgeIsPrivate.value = type
  showKnowledge.value = true
}

const selectedModel = ref('openchat')
const options = ref([])
const handleModelChange = () => {
  localStorage.setItem(CHAT_MODEL.GENERAL, selectedModel.value)
}
const getModels = async () => {
  const models = await httpModels()
  if (models) {
    options.value = models.list || []
  }
}
getModels()
onMounted(async () => {
  addStart = !!route.query.add
  if (appStore.useApp.id) {
    await handleSelectApp(appStore.useApp)
    appStore.clearUseApp()
  } else {
    await getChatHistories()
    initAppInputForm()
  }
  nextTick(() => {
    question.value && requestChat(question.value)
  })
  selectedModel.value = localStorage.getItem(CHAT_MODEL.GENERAL) || 'openchat'
})
</script>

<template>
  <div class="chat-list-left">
    <div class="app-input-list" v-if="appInfo.id && showAppInput">
      <div
        class="list-header clear-float-after text-center font-size-3xl flex justify-content-center"
      >
        <el-icon class="app-input-back" @click="handleClearApp">
          <component is="ArrowLeft" />
        </el-icon>
        <div class="w-190 multi-line-text text-center">{{ appInfo.title }}</div>
      </div>
      <div class="chat-list-box">
        <div class="chat-list-g">
          <el-form
            ref="appInputForm"
            :model="appInputData"
            :rules="appInputRules"
            label-position="top"
            style="width: 259px"
          >
            <el-form-item
              v-for="(f, k) in appInfo.option_message"
              :prop="`field_${k}`"
              :label="f.title"
            >
              <el-input
                type="text"
                v-model="appInputData[`field_${k}`]"
                maxlength="200"
                :placeholder="f.placeholder"
                v-if="f.input_type === 'text'"
              />
              <el-input
                type="textarea"
                v-model="appInputData[`field_${k}`]"
                maxlength="2000"
                :placeholder="f.placeholder"
                v-if="f.input_type === 'textarea'"
                :rows="4"
              />
              <el-select
                v-model="appInputData[`field_${k}`]"
                :placeholder="f.placeholder"
                v-if="f.input_type === 'select'"
              >
                <el-option v-for="op in f.options" :label="op" :value="op" />
              </el-select>
              <el-radio-group
                v-model="appInputData[`field_${k}`]"
                class="radio-app-input"
                v-if="f.input_type === 'radio'"
              >
                <el-radio v-for="op in f.options" :value="op" :label="op" />
              </el-radio-group>
              <el-checkbox-group
                v-model="appInputData[`field_${k}`]"
                class="radio-app-input"
                v-if="f.input_type === 'checkbox'"
              >
                <el-checkbox v-for="op in f.options" :value="op" :label="op" />
              </el-checkbox-group>
            </el-form-item>
          </el-form>
          <el-button
            type="primary"
            size="large"
            class="pw-100"
            @click="handleSubmitApp"
            style="width: 259px !important"
            >确 定
          </el-button>
        </div>
      </div>
    </div>
    <div class="list-header clear-float-after">
      <el-input
        class="search-input"
        v-model="query.keyword"
        placeholder="查找(Enter = 搜索)"
        @keydown.enter="handleFilter"
      />
      <el-tooltip effect="dark" content="新会话" placement="top">
        <el-button
          class="add-btn"
          icon="Plus"
          type="primary"
          :disabled="disable"
          @click="addSession()"
        />
      </el-tooltip>
    </div>
    <div class="chat-list-box" v-loading="list_loading">
      <div class="chat-list-g">
        <div
          v-for="(item, index) in chat_histories"
          :key="index"
          class="el-button is-plain item"
          :class="selected === item.id ? 'el-button--primary' : ''"
          @click="selectChatItem(item)"
          v-show="filter_word === '' || item.title.includes(filter_word)"
        >
          {{ item.title }}
          <el-popconfirm
            confirm-button-text="是"
            cancel-button-text="否"
            title="确认删除当前聊天记录？"
            cancel-button-type="warning"
            @confirm="handleDelete(item.id, index)"
            @cancel="() => {}"
          >
            <template #reference>
              <el-icon class="icon" @click.stop="() => {}">
                <component is="Delete" />
              </el-icon>
            </template>
          </el-popconfirm>
        </div>
      </div>
    </div>
  </div>
  <div class="chat-content-right" v-loading="chat_load">
    <div class="chat-models">
      <el-select
        v-model="selectedModel"
        style="width: 120px"
        popper-class="select-dropdown"
        @change="handleModelChange"
      >
        <template #header>
          <span style="color: #333; font-size: 12px">模型选择:</span>
        </template>
        <el-option
          v-for="item in options"
          :key="item.key"
          :label="item.value"
          :value="item.key"
        />
      </el-select>
    </div>
    <chat-message
      :histories="histories"
      :ai-output="ai_output"
      :disable="disable"
      :is-out-act="is_out_act"
      :knowledge-name="knowledge_name"
      :clear-knowledge="clearKnowledge"
      :questions="questions"
      :question-loading="question_loading"
      :input-and-enter="requestChat"
      :refresh-questions="getQuestions"
      :show-app-input="showAppInput"
      :app-info="appInfo"
      :select-app="handleSelectApp"
    />
    <chat-input
      :disable="disable"
      :on-submit="requestChat"
      :knowledge-id="knowledge_id ? knowledge_id : 0"
      v-model:file-id="file_id"
      :default-files="chat_files"
      :file-update="updateFile"
      :stop-answer="stopRequest"
      :select-app="handleSelectApp"
      v-model:show-app-input="showAppInput"
      :show-knowledge="handShowKnowledge"
      :app-info="appInfo"
      :handle-show-app-input="handleShowAppInput"
    />
  </div>
  <knowledge-select
    :select-knowledge="selectKnowledge"
    :is-private="knowledgeIsPrivate"
    :show="showKnowledge"
    :hide-dialog="
      () => {
        showKnowledge = false
      }
    "
  />
</template>
<style lang="scss">
.radio-app-input {
  .el-radio {
    min-width: 30%;
  }

  .el-checkbox {
    min-width: 30%;
  }
}
</style>
<style scoped lang="scss">
.chat-list-left {
  width: 300px;
  border-right: 1px solid var(--el-border-color);
  height: 100%;
  padding: 20px;
  position: absolute;
  top: 0;
  left: 0;

  .app-input-list {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: var(--el-bg-color);
    padding: 20px;
    overflow-y: auto;
    z-index: 1;
    transition: color 0.2s, background-color 0.2s;

    .app-input-back {
      cursor: pointer;
      font-size: 20px;
      position: absolute;
      left: 20px;
      top: 30px;
    }
  }

  .list-header {
    width: 100%;
    padding-bottom: 20px;
    border-bottom: 1px solid var(--el-border-color);

    .search-input {
      float: left;
      width: calc(100% - 60px);
    }

    .add-btn {
      float: right;
    }
  }

  .chat-list-box {
    margin-top: 20px;
    height: calc(100% - 80px);
    overflow: hidden;

    .chat-list-g {
      width: calc(100% + 50px);
      height: 100%;
      overflow-y: auto;
      overflow-x: hidden;
      .item {
        .icon {
          display: none;
        }
        &:hover .icon {
          display: inline-block;
        }
      }
    }

    .item {
      display: block;
      margin-left: 0;
      width: calc(100% - 50px);
      margin-top: 10px;
      text-align: left;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
      position: relative;
      padding-right: 30px;

      &.el-button--primary {
        &:hover {
          background-color: var(--el-color-primary-light-8);
          color: var(--el-color-primary);
        }
      }

      &:first-child {
        margin-top: 0;
      }

      .icon {
        position: absolute;
        right: 8px;
        top: 8px;
      }
    }
  }
}

.chat-content-right {
  width: calc(100vw - 440px);
  float: right;
  height: 100%;
  position: relative;
  overflow-x: hidden;
  .chat-models {
    position: fixed;
    display: flex;
    justify-content: center;
    top: 0;
    left: calc(50% + 150px);
    height: 30px;
    width: fit-content;
    transform: translateX(-50%);
    background-color: transparent;
    z-index: 999;
    .el-select {
      z-index: 999;
      background-color: transparent !important;
      :deep(.el-select__selected-item) {
        text-align: center;
      }
      :deep(.el-select__wrapper) {
        // background-color: rgba($color: #fff, $alpha: 0.1);
        border: none;
        border-bottom-left-radius: 8px;
        border-bottom-right-radius: 8px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        // padding: 2px 8px;
        .el-input__wrapper {
          background-color: transparent;
          box-shadow: none !important;
        }
        .el-input__inner {
          color: var(--el-color-primary);
          font-size: 13px;
        }
        // .el-select__icon {
        //   display: none;
        // }
        .el-input__inner {
          text-align: center;
        }
      }
    }
  }
}
</style>
